package org.dreamwork.cai.spring;

import com.google.gson.GsonBuilder;
import org.dreamwork.cai.SRFException;
import org.dreamwork.cai.annotation.ARemoteServiceDiscoverer;
import org.dreamwork.cai.core.IRemoteServices;
import org.dreamwork.cai.core.SRFBroadcastReceiver;
import org.dreamwork.cai.io.IRemoteServiceProvider;
import org.dreamwork.cai.jmx.CrossApplicationInvocation;
import org.dreamwork.cai.srf.ServiceRouteFrame;
import org.dreamwork.cai.srf.ServiceRouteTable;
import org.dreamwork.cai.tools.Const;
import org.dreamwork.cai.tools.Misc;
import org.dreamwork.concurrent.broadcast.ILocalBroadcastService;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;

import java.io.IOException;

public abstract class SpringRemoteServiceDiscovererBridge implements CommandLineRunner {
    private final Logger logger = LoggerFactory.getLogger (SpringRemoteServiceDiscovererBridge.class);

    @Autowired
    private ApplicationContext context;

    @Autowired
    private ServiceRouteTable table;

    @Autowired
    private ILocalBroadcastService broadcaster;

    @Value ("${cai.router.heartbeat.scan.interval:1000}")
    private int scanInterval;

    @Value ("${cai.router.heartbeat.send.interval:10000}")
    private int sendInterval;

    @Value ("${cai.router.heartbeat.timeout:60}")
    private int timeout;

    @Value ("${cai.router.broadcast.port:12315}")
    private int routeBroadcastPort;

    @Value ("${cai.router.provider.port:12316}")
    private int providerPort;

    @Value ("${cai.router.namespace}")
    private String namespace;

    private boolean autowire;

    @Override
    public void run (String... args) throws Exception {
        if (logger.isTraceEnabled ()) {
            logger.trace ("============= Spring Remote Service Discoverer Bridge =============");
            logger.trace ("= scan.interval     = {}s.", scanInterval / 1000);
            logger.trace ("= send.interval     = {}s.", sendInterval / 1000);
            logger.trace ("= heartbeat.timeout = {}s.", timeout);
            logger.trace ("= broadcast.port    = {}", routeBroadcastPort);
            logger.trace ("= provider.port     = {}", providerPort);
            logger.trace ("= local.namespace   = {}", namespace);
            logger.trace ("===================================================================");
        }
        SpringRemoteServicesBridge bridge = context.getBean (SpringRemoteServicesBridge.class);
        bridge.setServiceRouteTable (table);
        bridge.setBroadcast (broadcaster);

        // 解析本地 namespace
        resolveNamespace ();

        // 启动 cai jmx 服务器
        SpringServiceResolver resolver = context.getBean (SpringServiceResolver.class);
        resolver.setNamespace (this.namespace);

        CrossApplicationInvocation invocation = new CrossApplicationInvocation ();
        invocation.setResolver (resolver);
        context.getBean (SpringServiceManagerBridge.class).setCrossApplicationInvocation (invocation);

        // 为已经配置好的 spring 容器打补丁
        patchSpringContext ();

        // 注册本地广播的 关于 SRF 变化的监听器
        SRFBroadcastReceiver receiver = new SRFBroadcastReceiver (context.getBean (IRemoteServices.class));
        receiver.setBroadcaster (broadcaster);
        ARemoteServiceDiscoverer rsd = getClass ().getAnnotation (ARemoteServiceDiscoverer.class);
        if (rsd != null) {
            receiver.setBuildLocalStub (rsd.buildLocalStub ());
        }
        broadcaster.register (Const.Category.CAT_SRF_EVENT, receiver);

        // 给全局的路由表添加本地广播服务
        table.setBroadcaster (broadcaster);
    }

    /**
     * 扫描已经自动配置到 spring 中的，并且已经标注为远程服务的组件
     */
    private void patchSpringContext () throws SRFException, IOException {
        DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory ();

        SpringRemoteServicesBridge bridge = factory.getBean (SpringRemoteServicesBridge.class);
        bridge.setContext (context);
        bridge.setAutowireSupported (autowire);

        // 启动远程服务提供程序
        factory.getBean (IRemoteServiceProvider.class).bind (providerPort);

        // 构建本地 SRF
        ServiceRouteFrame srf = new ServiceRouteFrame (namespace, "127.0.0.1", providerPort);
        for (String beanName : factory.getBeanDefinitionNames ()) {
            Object bean = factory.getBean (beanName);
            Class<?> type = bean.getClass ();

            Misc.buildLocalSRF (type, srf, bean, beanName);
        }

        srf.udpPort = (int) (Math.random () * 5534 + 60000);
        if (!srf.items.isEmpty ()) {
            // 生成local SRF 的 udp 端口
            table.add (srf);
        }

        if (logger.isTraceEnabled ()) {
            logger.trace ("local SRF build completed");
            logger.trace ("\r\n{}", new GsonBuilder ().setPrettyPrinting ().create ().toJson (srf));
        }

        {
            // 启动路由广播器
            SpringUdpRouteBroadcasterBridge broadcaster = factory.getBean (SpringUdpRouteBroadcasterBridge.class);
            broadcaster.setSenderPort (srf.udpPort);
            broadcaster.bind (routeBroadcastPort);
            // 发送路由广播
            if (!srf.items.isEmpty ()) {
                broadcaster.update (srf);
            } else if (logger.isTraceEnabled ()) {
                logger.trace ("there's no local service frame to update. do nothing");
            }
        }

        {
            // 启动心跳扫描管理器
            SpringHeartbeatManagerBridge manager = factory.getBean (SpringHeartbeatManagerBridge.class);
            manager.setScanInterval (scanInterval);
            manager.setSendInterval (sendInterval);
            manager.setTimeout (timeout);
            manager.start ();
        }
    }

    private void resolveNamespace () {
        String namespace = null;
        if (getClass ().isAnnotationPresent (ARemoteServiceDiscoverer.class)) {
            ARemoteServiceDiscoverer rsd = getClass ().getAnnotation (ARemoteServiceDiscoverer.class);
            if (!StringUtil.isEmpty (rsd.namespace ())) {
                namespace = rsd.namespace ();
            }

            autowire = rsd.autowireRemoteServices ();
        }
        if (!StringUtil.isEmpty (namespace)) {
            this.namespace = namespace;
        }
        if (logger.isTraceEnabled ()) {
            logger.trace ("local namespace = {}", this.namespace);
        }
    }
}